/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.drawee.drawable;

import android.graphics.Matrix;
import android.graphics.Rect;

/**
 * Performs scale type calculations.
 */
public class ScalingUtils {

    /**
     * Options for scaling the child bounds to the parent bounds.
     * <p/>
     * Similar to {@link android.widget.ImageView.ScaleType}, but ScaleType.MATRIX is not supported.
     * To use matrix scaling, use a {@link MatrixDrawable}. An additional scale type (FOCUS_CROP) is
     * provided.
     */
    public enum ScaleType {

        /**
         * Scales width and height independently, so that the child matches the parent exactly.
         * This may change the aspect ratio of the child.
         */
        FIT_XY,

        /**
         * Scales the child so that it fits entirely inside the parent. At least one dimension (width or
         * height) will fit exactly. Aspect ratio is preserved.
         * Child is aligned to the top-left corner of the parent.
         */
        FIT_START,

        /**
         * Scales the child so that it fits entirely inside the parent. At least one dimension (width or
         * height) will fit exactly. Aspect ratio is preserved.
         * Child is centered within the parent's bounds.
         */
        FIT_CENTER,

        /**
         * Scales the child so that it fits entirely inside the parent. At least one dimension (width or
         * height) will fit exactly. Aspect ratio is preserved.
         * Child is aligned to the bottom-right corner of the parent.
         */
        FIT_END,

        /**
         * Performs no scaling.
         * Child is centered within parent's bounds.
         */
        CENTER,

        /**
         * Scales the child so that it fits entirely inside the parent. Unlike FIT_CENTER, if the child
         * is smaller, no up-scaling will be performed. Aspect ratio is preserved.
         * Child is centered within parent's bounds.
         */
        CENTER_INSIDE,

        /**
         * Scales the child so that both dimensions will be greater than or equal to the corresponding
         * dimension of the parent. At least one dimension (width or height) will fit exactly.
         * Child is centered within parent's bounds.
         */
        CENTER_CROP,

        /**
         * Scales the child so that both dimensions will be greater than or equal to the corresponding
         * dimension of the parent. At least one dimension (width or height) will fit exactly.
         * The child's focus point will be centered within the parent's bounds as much as possible
         * without leaving empty space.
         * It is guaranteed that the focus point will be visible and centered as much as possible.
         * If the focus point is set to (0.5f, 0.5f), result will be equivalent to CENTER_CROP.
         */
        FOCUS_CROP;

        /**
         * Gets the scale type out of string.
         * <p/>
         * <p> Used by GenericDraweeView styleable in
         * android_res/com/facebook/custom/res/values/attrs.xml
         *
         * @param value string value to parse
         * @return scale type if recognized
         * @throws IllegalArgumentException if scale type is not recognized
         */
        public static ScaleType fromString(String value) {
            if (value.equals("none")) {
                return null;
            } else if (value.equals("fitXY")) {
                return ScaleType.FIT_XY;
            } else if (value.equals("fitStart")) {
                return ScaleType.FIT_START;
            } else if (value.equals("fitCenter")) {
                return FIT_CENTER;
            } else if (value.equals("fitEnd")) {
                return FIT_END;
            } else if (value.equals("center")) {
                return CENTER;
            } else if (value.equals("centerInside")) {
                return CENTER_INSIDE;
            } else if (value.equals("centerCrop")) {
                return CENTER_CROP;
            } else if (value.equals("focusCrop")) {
                return FOCUS_CROP;
            } else {
                throw new IllegalArgumentException(
                        "Unrecognized scale type: " + value +
                                "; use a value defined in the ScalingUtils.fromString method");
            }
        }
    }

    /**
     * Gets transformation based on the scale type.
     *
     * @param transform    out matrix to store result
     * @param parentBounds parent bounds
     * @param childWidth   child width
     * @param childHeight  child height
     * @param focusX       focus point x coordinate, relative [0...1]
     * @param focusY       focus point y coordinate, relative [0...1]
     * @param scaleType    scale type to be used
     * @return reference to the out matrix
     */
    public static Matrix getTransform(
            final Matrix transform,
            final Rect parentBounds,
            final int childWidth,
            final int childHeight,
            final float focusX,
            final float focusY,
            final ScaleType scaleType) {

        final int parentWidth = parentBounds.width();
        final int parentHeight = parentBounds.height();

        final float scaleX = (float) parentWidth / (float) childWidth;
        final float scaleY = (float) parentHeight / (float) childHeight;

        float scale = 1.0f;
        float dx = 0;
        float dy = 0;


        switch (scaleType) {
            case FIT_XY:
                dx = parentBounds.left;
                dy = parentBounds.top;
                transform.setScale(scaleX, scaleY);
                transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            case FIT_START:
                scale = Math.min(scaleX, scaleY);
                dx = parentBounds.left;
                dy = parentBounds.top;
                transform.setScale(scale, scale);
                transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            case FIT_CENTER:
                scale = Math.min(scaleX, scaleY);
                dx = parentBounds.left + (parentWidth - childWidth * scale) * 0.5f;
                dy = parentBounds.top + (parentHeight - childHeight * scale) * 0.5f;
                transform.setScale(scale, scale);
                transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            case FIT_END:
                scale = Math.min(scaleX, scaleY);
                dx = parentBounds.left + (parentWidth - childWidth * scale);
                dy = parentBounds.top + (parentHeight - childHeight * scale);
                transform.setScale(scale, scale);
                transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            case CENTER:
                dx = parentBounds.left + (parentWidth - childWidth) * 0.5f;
                dy = parentBounds.top + (parentHeight - childHeight) * 0.5f;
                transform.setTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            case CENTER_INSIDE:
                scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
                dx = parentBounds.left + (parentWidth - childWidth * scale) * 0.5f;
                dy = parentBounds.top + (parentHeight - childHeight * scale) * 0.5f;
                transform.setScale(scale, scale);
                transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            case CENTER_CROP:
                if (scaleY > scaleX) {
                    scale = scaleY;
                    dx = parentBounds.left + (parentWidth - childWidth * scale) * 0.5f;
                    dy = parentBounds.top;
                } else {
                    scale = scaleX;
                    dx = parentBounds.left;
                    dy = parentBounds.top + (parentHeight - childHeight * scale) * 0.5f;
                }
                transform.setScale(scale, scale);
                transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            case FOCUS_CROP:
                if (scaleY > scaleX) {
                    scale = scaleY;
                    dx = parentWidth * 0.5f - childWidth * scale * focusX;
                    dx = parentBounds.left + Math.max(Math.min(dx, 0), parentWidth - childWidth * scale);
                    dy = parentBounds.top;
                } else {
                    scale = scaleX;
                    dx = parentBounds.left;
                    dy = parentHeight * 0.5f - childHeight * scale * focusY;
                    dy = parentBounds.top + Math.max(Math.min(dy, 0), parentHeight - childHeight * scale);
                }
                transform.setScale(scale, scale);
                transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
                break;

            default:
                throw new UnsupportedOperationException("Unsupported scale type: " + scaleType);
        }

        return transform;
    }
}
